#ifndef CLUSNET_BASE_OBJECTPOOL_H
#define CLUSNET_BASE_OBJECTPOOL_H

#include <clusnet/base/Exception.h>

#include <cassert>
#include <memory>
#include <limits>
#include <list>

namespace clusnet
{
namespace base
{

template<typename T, typename Alloc = std::allocator<T>>
class ObjectPool final
{
    public:
        using pointer = typename Alloc::pointer;
    public:
        ObjectPool(const ObjectPool<T, Alloc>&) = delete;
        ObjectPool<T, Alloc>& operator= (const ObjectPool<T, Alloc>&) = delete;
        explicit ObjectPool(size_t chunkSize = kDefaultSize, size_t increaseSize = kIncreaseSize, const Alloc& allocator = Alloc())
            : freeList_(),
              chunkSize_(chunkSize),
              increaseSize_(increaseSize),
              allocator_(allocator),
              totalAllocNum_(0)
        {
            assert(chunkSize_ > 0 && increaseSize_ > 0);
            allocate(chunkSize_);
        }
        ~ObjectPool()
        {
            destroy();
        }

    public:
        template<typename... Args, typename = typename std::enable_if<std::is_constructible<T, Args...>::value>::type>
        std::shared_ptr<T> acquire(Args&&... args)
        {
            pointer obj = getFree();
            allocator_.construct(obj, std::forward<Args>(args)...);
            return std::shared_ptr<T>(obj, std::bind(&ObjectPool<T, Alloc>::release, this, std::placeholders::_1));
        }

        template<typename... Args, typename = typename std::enable_if<std::is_constructible<T, Args...>::value>::type>
        std::shared_ptr<T> acquireWithDelHandler(const std::function<void(pointer)>& delHandler, Args&&... args)
        {
            pointer obj = getFree();
            allocator_.construct(obj, std::forward<Args>(args)...);
            return std::shared_ptr<T>(obj, delHandler);
        }

        void release(pointer obj)
        {
            deallocate(obj);
        }

        bool checkLeak() const
        {
            return totalAllocNum_ != freeList_.size();
        }
 
    private:
        pointer getFree()
        {
            if (freeList_.empty())
            {
                allocate(increaseSize_);
            }
            pointer result = freeList_.back();
            assert(result != nullptr);
            freeList_.pop_back();
            return result;
        }

        void allocate(size_t n)
        {
            assert(freeList_.empty());
            if (n > max_size())
            {
                throw clusnet::base::Exception("object pool length error");
            }
            for (size_t i = 0; i < n; ++i)
            {
                freeList_.push_back(allocator_.allocate(1));
                ++totalAllocNum_;
            }
        }

        void deallocate(pointer obj)
        {
            allocator_.destroy(obj);
            freeList_.push_back(obj);
        }

        size_t max_size() const
        {
            return std::min<size_t>(allocator_.max_size(), std::numeric_limits<typename Alloc::difference_type>::max());
        }

        void destroy()
        {
            for(auto& elem : freeList_)
            {
                allocator_.deallocate(elem, 1);
            }
        }

    private:
        static const size_t kDefaultSize = 0x00000080;  // 128
        static const size_t kIncreaseSize = 0x00000400; //1024

        std::list<pointer>      freeList_;
        size_t                  chunkSize_;
        size_t                  increaseSize_;
        Alloc                   allocator_;

        size_t                  totalAllocNum_;
};
}
}

#endif 
